<?php

BaseMvc::includeLib('Session', 'session', true);

/**
 * Adds security against session stealling.
 * It makes it harder to steal the session.
 *
 * @author Felix Balfoort
 * @version 0.1
 */
class SecuredSession extends Session
{

    private $settings = array('HTTPUSERAGENT_NAME' => 'HTTP_USER_AGENT', 'REMOTEADDR_NAME' => 'REMOTE_ADDR', 'COOKIE_SECUREDSESSIONID' => 'secured_session_id', 'COOKIE_SECUREDSESSIONID_TIME' => 500, 'SESSION_SECUREDSESSIONVAR' => 'secured_session_var');
    private $salt = null;
    private $userKey = '';

    public function __construct($salt, $settings = false) {
        if (is_array($settings)) {
            array_merge($this->settings, $settings);
        }

        $id = session_id();
        if (empty($id)) {
            session_start();
        }

        session_regenerate_id();

        if (empty($salt)) {
            throw new SecuredSessionException("Salt cannot be empty", SecuredSessionException::EMPTYSALT);
        } else {
            $this->salt = $salt;
        }

        $this->validateUser();
    }

    private function validateUser() {
        if (empty($_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']])) {
            $_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['id'] = $this->generateUserKey();
            $_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['number'] = rand(1, 100000000);
            setcookie($this->settings['COOKIE_SECUREDSESSIONID'], $this->generateCookieKey(), time() + $this->settings['COOKIE_SECUREDSESSIONID']);
        } else if (empty($_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['id']) || $_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['id'] != $this->generateUserKey()) {
            $this->validationFailed();
        } else if (empty($_COOKIE[$this->settings['COOKIE_SECUREDSESSIONID']]) || $_COOKIE[$this->settings['COOKIE_SECUREDSESSIONID']] != $this->generateCookieKey()) {
            $this->validationFailed();
        } else {
            $_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['number'] = rand(1, 100000000);
            setcookie($this->settings['COOKIE_SECUREDSESSIONID'], $this->generateCookieKey(), time() + $this->settings['COOKIE_SECUREDSESSIONID']);
        }
    }

    public function destructSession() {
        setcookie($this->settings['COOKIE_SECUREDSESSIONID'], '', time() - 3600);
        parent::destructSession();
    }

    private function validationFailed() {
        $this->destructSession();
        throw new SecuredSessionException("User couldn't be validated", SecuredSessionException::VALIDATEUSERFAILED);
    }

    /**
     * Generates the user key this value is cached in userkey cause it isn't affected bij the runtime after it is made.
     * @return String
     */
    private function generateUserKey() {
        return!empty($this->userKey) ? $this->userKey : $this->userKey = $this->hashKey($_SERVER[$this->settings['HTTPUSERAGENT_NAME']] . $_SERVER[$this->settings['REMOTEADDR_NAME']] . $this->salt);
    }

    private function generateCookieKey() {
        return $this->hashKey($this->salt . $_SESSION[$this->settings['SESSION_SECUREDSESSIONVAR']]['number'] . $this->generateUserKey());
    }

    private function hashKey($key) {
        return sha1($key);
    }

}

class SecuredSessionException extends Exception
{
    const EMPTYSALT = 1, VALIDATEUSERFAILED = 2;
}